import {TaskPoolTaskBase} from "./TaskPoolTaskBase";
import {ITaskAgent} from "./ITaskAgent";
import LinkListNode, {LinkList} from "../../../Utils/LinkList";
import {ReferencePool} from "../ReferencePool/ReferencePool";
import {TaskInfo} from "./TaskInfo";
import {TaskPoolTaskStatus} from "./TaskPoolTaskStatus";
import {StartTaskStatus} from "./StartTaskStatus";

export class TaskPool<T extends TaskPoolTaskBase> {
    get paused(): boolean {
        return this._paused;
    }

    set paused(value: boolean) {
        this._paused = value;
    }

    private readonly _freeAgents: Stack<ITaskAgent<T>>;
    private readonly _workingAgents: LinkList<ITaskAgent<T>>;
    private readonly _waitingTasks: LinkList<T>;
    private _paused: boolean;


    constructor() {
        this._freeAgents = new Stack();
        this._waitingTasks = new LinkList();
        this._workingAgents = new LinkList();
        this._paused = false;
    }

    get TotalAgentCount() {
        return this.FreeAgentCount + this.WorkingAgentCount;
    }

    get FreeAgentCount() {
        return this._freeAgents.size();
    }

    get WorkingAgentCount() {
        return this._workingAgents.Size();
    }

    get WaitingTaskCount() {
        return this._waitingTasks.Size();
    }

    Update(elapseSeconds: number, realElapseSeconds: number): void {
        if (this.paused) {
            return;
        }
        this.ProcessRunningTasks(elapseSeconds, realElapseSeconds);
        this.ProcessWaitingTasks(elapseSeconds, realElapseSeconds);
    }

    AddAgent(agent: ITaskAgent<T>): void {
        if (agent == null) {
            throw new Error("Task agent is invalid.");
        }
        agent.Initialize();
        this._freeAgents.push(agent);
    }

    AddTask(task: T): void {
        let current: LinkListNode<T> = this._waitingTasks.Last;
        while (current != null) {
            if (task.priority <= current.Value.priority) {
                break;
            }

            current = current.Previous;
        }

        if (current != null) {
            this._waitingTasks.AddAfter(current, task);
        } else {
            this._waitingTasks.AddFirst(task);
        }
    }


    Shutdown(): void {
        this.RemoveAllTasks();

        while (!this._freeAgents.empty()) {
            this._freeAgents.pop().Shutdown();
        }
    }

    public RemoveTask(serialId: number): boolean {
        this._waitingTasks.ForEach(value => {
            if (value.serialId == serialId) {
                this._waitingTasks.Remove(value);
                ReferencePool.Release(value);
                return true;
            }
        });
        this._workingAgents.ForEach(workingAgent => {
            if (workingAgent.Task().serialId == serialId) {
                let task = workingAgent.Task();
                workingAgent.Reset();
                this._freeAgents.push(workingAgent);
                this._workingAgents.Remove(workingAgent);
                ReferencePool.Release(task);
                return true;
            }
        })
        return false;
    }

    RemoveAllTasks() {
        this._waitingTasks.ForEach(value => {
            ReferencePool.Release(value);
            return false;
        });
        this._waitingTasks.Clear();
        this._workingAgents.ForEach(value => {
            let task = value.Task();
            value.Reset();
            this._freeAgents.push(value);
            ReferencePool.Release(task);
            return false;
        });
        this._workingAgents.Clear();
    }

    GetAllTaskInfos(): TaskInfo[] {
        let results = [];
        this._workingAgents.ForEach(value => {
            let workingTask = value.Task();
            results.push(new TaskInfo(workingTask.serialId, workingTask.priority, workingTask.done ? TaskPoolTaskStatus.Done : TaskPoolTaskStatus.Doing, workingTask.description));
            return false;
        });
        this._waitingTasks.ForEach(value => {
            let workingTask = value;
            results.push(new TaskInfo(workingTask.serialId, workingTask.priority, TaskPoolTaskStatus.Todo, workingTask.description));
            return false;
        })
        return results;
    }


    private ProcessRunningTasks(elapseSeconds: number, realElapseSeconds: number) {
        let current: LinkListNode<ITaskAgent<T>> = this._workingAgents.First;
        while (current != null) {
            let task: T = current.Value.Task();
            if (!task.done) {
                current.Value.Update(elapseSeconds, realElapseSeconds);
                current = current.Next;
                continue;
            }

            let next: LinkListNode<ITaskAgent<T>> = current.Next;
            current.Value.Reset();
            this._freeAgents.push(current.Value);
            this._workingAgents.Remove2(current);
            ReferencePool.Release(task);
            current = next;
        }
    }

    private ProcessWaitingTasks(elapseSeconds: number, realElapseSeconds: number) {
        let current: LinkListNode<T> = this._waitingTasks.First;
        while (current != null && this.FreeAgentCount > 0) {
            let agent: ITaskAgent<T> = this._freeAgents.pop();
            let agentNode: LinkListNode<ITaskAgent<T>> = this._workingAgents.AddLast(agent);
            let task = current.Value;
            let next: LinkListNode<T> = current.Next;
            let status: StartTaskStatus = agent.Start(task);
            if (status == StartTaskStatus.Done || status == StartTaskStatus.HasToWait || status == StartTaskStatus.UnknownError) {
                agent.Reset();
                this._freeAgents.push(agent);
                this._workingAgents.Remove2(agentNode);
            }

            if (status == StartTaskStatus.Done || status == StartTaskStatus.CanResume || status == StartTaskStatus.UnknownError) {
                this._waitingTasks.Remove2(current);
            }

            if (status == StartTaskStatus.Done || status == StartTaskStatus.UnknownError) {
                ReferencePool.Release(task);
            }

            current = next;
        }
    }
}